package com.sudo.api._admin;

import com.sudo.common.annotation.ApiIdempotent;
import com.sudo.common.annotation.ApiAuthorization;
import com.sudo.common.component.RedisKeys;
import com.sudo.common.component.StringRedisUtil;
import com.sudo.common.constant.SysConfigConstant;
import com.sudo.common.constant.UserConstant;
import com.sudo.common.enums.SysStatus;
import com.sudo.common.enums.SysUserSource;
import com.sudo.common.utils.*;
import com.sudo.dao.admin.entity.SysConfig;
import com.sudo.dao.admin.entity.SysRole;
import com.sudo.dao.admin.entity.SysUser;
import com.sudo.dao.admin.model.bo.*;
import com.sudo.dao.admin.model.bo.sysconfig.SysUserRegistDTO;
import com.sudo.dao.admin.model.vo.SysSimpleUserVO;
import com.sudo.dao.admin.model.vo.SysUserReVO;
import com.sudo.dao.admin.model.vo.SysUserTokenVO;
import com.sudo.service.admin.mail.MailPojo;
import com.sudo.service.admin.mail.MailPojoUtil;
import com.sudo.service.admin.mail.MailService;
import com.sudo.service.admin.service.SysConfigService;
import com.sudo.service.admin.service.SysRoleService;
import com.sudo.service.admin.service.SysUserLoginRecordService;
import com.sudo.service.admin.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Api(value = "用户接口", tags = {"用户相关的接口"})
@RestController
@RequestMapping("sysUser")
public class SysUserController extends AdminBaseController {
    @Resource
    public StringRedisUtil stringRedisUtil;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysUserLoginRecordService sysUserLoginRecordService;
    @Resource
    private SysConfigService sysConfigService;
    @Resource
    private MailService mailService;
    @Resource
    private RedisKeys redisKeys;

    //生成验证码图片
    @ApiIdempotent
    @ApiOperation(value = "获取验证码", notes = "用户登录或注册获取验证码", httpMethod = "GET")
    @GetMapping("generateImageCode")
    public void generateImageCode(HttpServletRequest request,HttpServletResponse response) throws Exception {
        //生成随机四位验证码
        String code = VerifyCodeUtil.generateVerifyCode(4);
        String uniqueClient = HttpUserInfoUtil.getUniqueClient(request);

        //保存到redis中，设置有效期2分钟
        stringRedisUtil.set(redisKeys.redisPictureVerifyCode(uniqueClient), code, 120);
        //设置响应类型
        response.setContentType("image/png");
        //根据随机数生成图片，通过response响应图片
        ServletOutputStream os = response.getOutputStream();
        VerifyCodeUtil.outputImage(150,34,os,code);
    }
    /**
     * 用户登录
     *
     * @param mailOrUsername 账户
     * @param pwd            密码
     * @param request        req
     * @param response       resp
     * @return JsonRes
     */
    @ApiIdempotent
    @ApiOperation(value = "用户登录", notes = "采用账号或邮箱登录", httpMethod = "POST")
    @PostMapping("userLogin")
    public JsonRes userLogin(@RequestParam String mailOrUsername,
                             @RequestParam String pwd,
                             @RequestParam String imageCode,
                             HttpServletRequest request,
                             HttpServletResponse response) {
        if (StringUtils.isBlank(mailOrUsername)) {
            log.warn("账号不能为空");
            return JsonRes.errorMsg("账号不能为空");
        }
        if (StringUtils.isBlank(pwd)) {
            log.warn("密码不能为空");
            return JsonRes.errorMsg("密码不能为空");
        }
        if (StringUtils.isBlank(imageCode)) {
            log.warn("验证码不能为空");
            return JsonRes.errorMsg("验证码不能为空");
        }
        String redis_verify_code = redisKeys.redisPictureVerifyCode(HttpUserInfoUtil.getUniqueClient(request));
        String imageCodeFromRedis = stringRedisUtil.get(redis_verify_code);
        stringRedisUtil.expire(redis_verify_code,0,TimeUnit.SECONDS);
        if (StringUtils.isBlank(imageCodeFromRedis)) {
            log.warn("验证码已过期");
            return JsonRes.errorMsg("验证码已过期");
        }

        if (!imageCodeFromRedis.equals(imageCode)) {
            log.warn("验证码不正确");
            return JsonRes.errorMsg("验证码不正确");
        }
        // 判断是否为邮箱
        boolean isEmail = PatternUtil.isMatches(PatternUtil.email, mailOrUsername);
        SysUser sysUser;
        if (isEmail){
            // 邮箱
            sysUser = sysUserService.getSysUserByMail(mailOrUsername);
        }else {
            // 账号
            sysUser = sysUserService.getSysUserByUsername(mailOrUsername);
        }
        if (sysUser == null){
            String emailNotExist = "登录失败，邮箱【" + mailOrUsername + "】不存在";
            String usernameNotExist = "登录失败，账号【" + mailOrUsername + "】不存在";
            if (isEmail){
                log.warn(emailNotExist);
                return JsonRes.errorMsg(emailNotExist);
            }else {
                log.warn(usernameNotExist);
                return JsonRes.errorMsg(usernameNotExist);
            }
        }else {
            // 判断密码是否匹配
            String md5Pwd = MD5Util.getMD5Str(pwd);
            if (SysStatus.OFF.name().equals(sysUser.getStatus())) {
                log.warn("登录失败，用户【" + mailOrUsername + "已被禁止登陆");
                return JsonRes.errorMsg("登录失败，该用户已被禁止登录");
            }
            if (!sysUser.getPassword().equals(md5Pwd)) {
                log.warn("登录失败，用户【" + mailOrUsername + "】密码不匹配");
                return JsonRes.errorMsg("登录失败，用户密码不匹配");
            }
        }
        //如果角色被禁止不能登录
        SysRole sysRole = sysRoleService.getRoleByUsername(sysUser.getUsername());
        if (SysStatus.OFF.name().equals(sysRole.getStatus())) {
            log.warn("登录失败，该角色已被禁止登陆");
            return JsonRes.errorMsg("登录失败，该角色已被禁止登陆");
        }

        SysUserTokenVO sysUserVO = new SysUserTokenVO();
        BeanUtils.copyProperties(sysUser,sysUserVO);

        //实现用户的redis会话
        String uniqueToken = RandomUtil.getUUID();
        stringRedisUtil.set(redisKeys.redisUserToken(sysUser.getUsername()), uniqueToken, 7200);

        sysUserVO.setUniqueToken(uniqueToken);
        //设置cookie
        CookieUtil.setCookie(request,response, UserConstant.COOKIE_NAME,
                JsonUtil.objectToJson(sysUserVO),true);
        ThreadExecutor.execute(() -> sysUserLoginRecordService.addUserLoginRecord(sysUser.getUsername(), request));
        log.info("用户【" + mailOrUsername + "】登录成功");
        return JsonRes.ok(sysUserVO);
    }

    @ApiOperation(value = "判断用户token是否正常", notes = "判断用户token是否正常", httpMethod = "GET")
    @PostMapping("/isUserTokenRight")
    public JsonRes isUserTokenRight(HttpServletRequest request) {
        String username = HttpUserInfoUtil.getSessionUser(request);
        String userToken = HttpUserInfoUtil.getSessionToken(request);
        String redisToken = stringRedisUtil.get(redisKeys.redisUserToken(username));
        if (userToken.equals(redisToken)) {
            return JsonRes.ok();
        }
        return JsonRes.errorMsg("用户信息已失效，请重新登录");
    }


    @ApiIdempotent
    @ApiOperation(value = "用户退出登录", notes = "用户退出登录，清除会话和cookie", httpMethod = "POST")
    @PostMapping("/logout")
    public JsonRes logout(HttpServletRequest request,
                          HttpServletResponse response) {
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        //清除用户相关的cookie信息
        CookieUtil.deleteCookie(request, response, UserConstant.COOKIE_NAME);
        //用户退出登录，清除redis中user的回话信息
        stringRedisUtil.delete(redisKeys.redisUserToken(sessionUser));
        return JsonRes.ok();
    }


    @ApiOperation(value = "分页查询所有用户核心信息", notes = "分页查询所有用户核心信息，进行管理", httpMethod = "GET")
    @GetMapping("/queryAllSysUserMainInfoByPage")
    public JsonRes queryAllSysUserMainInfoByPage(@RequestParam String usernameOrNick,
                                                 @RequestParam Integer page,
                                                 @RequestParam Integer pageSize) {
        PagedGridResult pagedGridResult = sysUserService.queryAllSysUserMainInfoByPage(page, pageSize, usernameOrNick);
        return JsonRes.ok(pagedGridResult);
    }

    @ApiOperation(value = "根据username查询用户详情和角色code、Name", notes = "根据username查询用户部分信息和角色code、Name，进行修改", httpMethod = "GET")
    @GetMapping("getSysUserAndRoleByUsername")
    public JsonRes getSysUserAndRoleByUsername(@RequestParam String username) {
        SysUserReVO sysUserReVO = sysUserService.getSysUserAndRoleByUsername(username);
        return JsonRes.ok(sysUserReVO);
    }


    @ApiIdempotent
    @ApiAuthorization(menu = "userRun", button = "add")
    @ApiOperation(value = "添加用户（包括角色关联关系）", notes = "添加用户（包括角色关联关系）", httpMethod = "POST")
    @PostMapping("addUser")
    public JsonRes addUser(@RequestBody @Valid SimpleUserBO simpleUserBO,
                           BindingResult result,
                           HttpServletRequest request) {
        if (result.hasErrors()) {
            return JsonRes.errorMsg(getFirstError(result));
        }
        //1.不允许添加超级管理员
        if (superAdmin(simpleUserBO.getRoleCode())) {
            return JsonRes.errorMsg("禁止添加超级管理员");
        }
        //2.判断username是否已存在
        SysUser sysUser = sysUserService.getSysUserWithoutPwdByUsername(simpleUserBO.getUsername());
        if (null != sysUser) {
            return JsonRes.errorMsg("该账号已存在");
        }
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);

        //3.判断角色是否存在
        boolean isHas = sysRoleService.hasRoleCode(simpleUserBO.getRoleCode());
        if (!isHas) {
            return JsonRes.errorMsg("当前角色不存在");
        }
        int affectCount = sysUserService.addUser(simpleUserBO, sessionUser);
        return JsonRes.judge(affectCount, "添加用户成功", "添加用户失败");
    }

    @ApiIdempotent
    @ApiAuthorization(menu = "userRun", button = "update_role_status")
    @ApiOperation(value = "切换用户的角色或状态", notes = "切换用户的角色或状态", httpMethod = "POST")
    @PostMapping("updateSysUserRoleStatus")
    public JsonRes updateSysUserRoleStatus(@RequestParam("username") String username,
                                           @RequestParam("roleCode") String roleCode,
                                           @RequestParam("status") String status,
                                           HttpServletRequest request) {
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        //1.只允许超级管理员操作
        if (!superName(sessionUser)) {
            return JsonRes.errorMsg("只允许超级管理员操作");
        }
        //2.不允许修改超级管理员
        if (superName(username)) {
            return JsonRes.errorMsg("不允许修改超级管理员账号");
        }
        //3.不允许切换为超级管理员
        if (superAdmin(roleCode)) {
            return JsonRes.errorMsg("不允许切换为超级管理员");
        }
        boolean isHas = sysRoleService.hasRoleCode(roleCode);
        if (!isHas) {
            return JsonRes.errorMsg("该角色不存在");
        }
        int affectCount = sysUserService.updateSysUserRoleStatus(username, roleCode, status);
        return JsonRes.judge(affectCount, "更新用户的角色或状态成功", "更新用户的角色或状态失败");
    }

    @ApiIdempotent
    @ApiAuthorization(menu = "userRun", button = "delete")
    @ApiOperation(value = "根据username删除用户", notes = "根据username删除用户", httpMethod = "POST")
    @PostMapping("deleteSysUserByUsername")
    public JsonRes deleteSysUserByUsername(@RequestParam("username") String username,
                                           HttpServletRequest request) {
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        //1.只允许超级用户操作
        if (!superName(sessionUser)) {
            return JsonRes.errorMsg("只允许超级管理员操作");
        }
        //2.不允许删除超级用户
        if (superName(username)) {
            return JsonRes.errorMsg("不允许删除超级用户");
        }
        int affectCount = sysUserService.deleteSysUserByUsername(username);
        return JsonRes.judge(affectCount, "删除用户成功", "删除用户失败");
    }

    @ApiOperation(value = "分页获取所有在线用户", notes = "分页获取所有在线用户", httpMethod = "GET")
    @GetMapping("getOnlineUserByPage")
    public JsonRes getOnlineUserByPage(@RequestParam("page") Integer page,
                                       @RequestParam("pageSize") Integer pageSize) {

        PagedGridResult pagedGridResult = sysUserService.getOnlineUserByPage(page, pageSize);
        return JsonRes.ok(pagedGridResult);
    }

    @ApiIdempotent
    @ApiAuthorization(menu = "user_online", button = "shotOff")
    @ApiOperation(value = "踢出在线用户", notes = "踢出在线用户", httpMethod = "POST")
    @PostMapping("shotOffOnlineUser")
    public JsonRes shotOffOnlineUser(@RequestParam("username") String username) {
        if (superName(username)) {
            return JsonRes.errorMsg("禁止踢出超级管理员");
        }
        stringRedisUtil.delete(redisKeys.redisUserToken(username));
        return JsonRes.okMsg("成功踢出用户");
    }

    @ApiOperation(value = "获取我的用户资料", notes = "获取我的用户资料", httpMethod = "GET")
    @GetMapping("getMyUserInfo")
    public JsonRes getMyUserInfo(HttpServletRequest request) {
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        SysUser sysUser = sysUserService.getSysUserWithoutPwdByUsername(sessionUser);
        return JsonRes.ok(sysUser);
    }

    @ApiOperation(value = "获取我的简单资料", notes = "获取我的简单资料", httpMethod = "GET")
    @GetMapping("getMySimpleUserInfo")
    public JsonRes getMySimpleUserInfo(HttpServletRequest request) {
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        SysSimpleUserVO sysSimpleUserVO = sysUserService.getMySimpleUserInfo(sessionUser);
        return JsonRes.ok(sysSimpleUserVO);
    }

    @ApiIdempotent
    @ApiOperation(value = "修改个人资料", notes = "修改个人资料", httpMethod = "POST")
    @PostMapping("updateMyUserInfo")
    public JsonRes updateMyUserInfo(@RequestBody @Valid SysUserBasicInfoBO basicInfoBO,
                                    BindingResult result,
                                    HttpServletRequest request) {
        if (result.hasErrors()) {
            return JsonRes.errorMsg(getFirstError(result));
        }
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        if (!sessionUser.equals(basicInfoBO.getUsername())) {
            return JsonRes.errorMsg("禁止修改他人资料");
        }
        int affectCount = sysUserService.updateMyUserInfo(basicInfoBO, sessionUser);
        return JsonRes.judge(affectCount, "修改个人资料成功", "修改个人资料失败");
    }

    @ApiIdempotent
    @ApiOperation(value = "修改我的密码", notes = "修改我的密码", httpMethod = "POST")
    @PostMapping("updateMyPassword")
    public JsonRes updateMyPassword(@RequestBody @Valid UpdateMyPwdBO updateMyPwdBO,
                                    BindingResult result,
                                    HttpServletRequest request) {
        if (result.hasErrors()) {
            return JsonRes.errorMsg(getFirstError(result));
        }
        // 1.验证 新密码和确认密码 是否一致
        String newPassword = updateMyPwdBO.getNewPassword().trim();
        String confirmPassword = updateMyPwdBO.getConfirmPassword().trim();
        if (!newPassword.equals(confirmPassword)) {
            return JsonRes.errorMsg("确认密码与新密码不一致");
        }
        // 2.判断用户是否存在
        String sessionUser = HttpUserInfoUtil.getSessionUser(request);
        SysUser sysUser = sysUserService.getSysUserByUsername(sessionUser);
        if (null == sysUser) {
            return JsonRes.errorMsg("当前用户不存在");
        }

        // 3.验证原始密码是否正确
        String oldPasswordMd5 = MD5Util.getMD5Str(updateMyPwdBO.getOldPassword());
        if (!oldPasswordMd5.equals(sysUser.getPassword())) {
            return JsonRes.errorMsg("原始密码不正确");
        }
        // 4.修改密码
        int affectCount = sysUserService.updateMyPassword(sessionUser, newPassword);
        return JsonRes.judge(affectCount, "修改密码成功", "修改密码失败");
    }

    // 发送邮箱验证码
    @ApiOperation(value = "发送邮箱验证码", notes = "用户注册获取邮箱验证码", httpMethod = "POST")
    @PostMapping("postEmailCode")
    public JsonRes postEmailCode(@RequestBody @Valid SysPostEmailCodeBO sysPostEmailCodeBO,
                                 BindingResult result,
                                 HttpServletRequest request) {
        if (result.hasErrors()) {
            return JsonRes.errorMsg(getFirstError(result));
        }
        if (!sysPostEmailCodeBO.getPassword().equals(sysPostEmailCodeBO.getConfirmPassword())) {
            return JsonRes.errorMsg("两次密码不一致");
        }
        //判断username是否已存在
        SysUser sysUser1 = sysUserService.getSysUserWithoutPwdByUsername(sysPostEmailCodeBO.getUsername());
        if (null != sysUser1) {
            return JsonRes.errorMsg("该账号已存在");
        }
        //判断email是否已存在
        SysUser sysUser2 = sysUserService.getSysUserWithoutPwdByMail(sysPostEmailCodeBO.getEmail());
        if (null != sysUser2) {
            return JsonRes.errorMsg("该邮箱已注册");
        }

        String uniqueClientEmail = HttpUserInfoUtil.getUniqueClientExtra(request,sysPostEmailCodeBO.getEmail());
        String key = redisKeys.redisEmailVerifyCode(uniqueClientEmail);
        // 解决幂等性问题
        String redisIdempotence = redisKeys.redisIdempotence(uniqueClientEmail);
        boolean setIfAbsent = stringRedisUtil.setIfAbsent(redisIdempotence, "idempotence",60L);
        // 多次点击
        if (!setIfAbsent){
            Long expire = stringRedisUtil.getExpire(redisIdempotence);
            return JsonRes.error(expire,"操作过于频繁，请稍后");
        }else {

            //生成随机验证码
            String code = RandomUtil.getRandomStr(4);
            stringRedisUtil.set(key,code,300);
            MailPojo mailPojo = MailPojoUtil.emailVerifyCode(sysPostEmailCodeBO.getEmail(), code);
            mailService.sendSimpleMail(mailPojo);
        }
        Long expire = stringRedisUtil.getExpire(redisIdempotence);
        return JsonRes.ok(expire,"验证码已发送至邮箱");
    }

    @ApiIdempotent
    @ApiOperation(value = "用户注册", notes = "用户注册", httpMethod = "POST")
    @PostMapping("userRegister")
    public JsonRes userRegister(@RequestBody @Valid SysUserRegisterBO sysUserRegisterBO,
                                   BindingResult result,
                                   HttpServletRequest request){
        if (result.hasErrors()) {
            return JsonRes.errorMsg(getFirstError(result));
        }
        if (!sysUserRegisterBO.getPassword().equals(sysUserRegisterBO.getConfirmPassword())) {
            return JsonRes.errorMsg("两次密码不一致");
        }
        //判断username是否已存在
        SysUser sysUser = sysUserService.getSysUserWithoutPwdByUsername(sysUserRegisterBO.getUsername());
        if (null != sysUser) {
            return JsonRes.errorMsg("该账号已存在");
        }
        //判断email是否已存在
        SysUser sysUser2 = sysUserService.getSysUserWithoutPwdByMail(sysUserRegisterBO.getEmail());
        if (null != sysUser2) {
            return JsonRes.errorMsg("该邮箱已注册");
        }
        String redis_verify_code = redisKeys.redisEmailVerifyCode(HttpUserInfoUtil.getUniqueClientExtra(request,sysUserRegisterBO.getEmail()));
        String emailCodeFromRedis = stringRedisUtil.get(redis_verify_code);
        if (StringUtils.isBlank(emailCodeFromRedis)) {
            log.warn("验证码已过期");
            return JsonRes.errorMsg("验证码已过期");
        }
        // 1.判断验证码是否正确
        if (!emailCodeFromRedis.equals(sysUserRegisterBO.getEmailCode())) {
            return JsonRes.errorMsg("验证码不匹配");
        }
        stringRedisUtil.expire(redis_verify_code,0,TimeUnit.SECONDS);
        // 4.判断当前注册模块是否存在，状态是否正常
        List<SysConfig> sysConfigList = sysConfigService.getSysConfigListByObject(SysConfigConstant.USER_REGIST);
        SysUserRegistDTO sysUserRegistDTO = SysUserRegistDTO.list2Bean(sysConfigList);
        if (null == sysUserRegistDTO || SysStatus.OFF.name().equals(sysUserRegistDTO.getStatus())) {
            return JsonRes.errorMsg("注册入口暂未开放");
        }
        int affectCount = sysUserService.userRegister(
                sysUserRegisterBO.getEmail(),
                sysUserRegisterBO.getUsername(),
                sysUserRegisterBO.getPassword(),
                SysUserSource.REGISTER, sysUserRegistDTO);
        return JsonRes.judge(affectCount, "注册成功", "注册失败，请联系管理员");
    }
}
